﻿using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Xml.Linq;
using ComTypes = System.Runtime.InteropServices.ComTypes;
using System.Xml.Serialization;

namespace NFox.Runtime.Com.Reflection
{

    /// <summary>
    /// 元素类型说明
    /// </summary>
    public class Element
    {

        public string Default;

        public Dictionary<string, bool> Flags { get; }

        public Element(ComTypes.ITypeInfo info, ComTypes.ELEMDESC desc)
        {
            var pdesc = desc.desc.paramdesc;
            ComTypes.PARAMFLAG flags = pdesc.wParamFlags;

            Flags =
                new Dictionary<string, bool>
                {
                    { "In",     (flags & ComTypes.PARAMFLAG.PARAMFLAG_FIN) != 0 },
                    { "Out",    (flags & ComTypes.PARAMFLAG.PARAMFLAG_FOUT) != 0 },
                    { "Lcid",   (flags & ComTypes.PARAMFLAG.PARAMFLAG_FLCID) != 0 },
                    { "Retval", (flags & ComTypes.PARAMFLAG.PARAMFLAG_FRETVAL) != 0 },
                    { "Option", (flags & ComTypes.PARAMFLAG.PARAMFLAG_FOPT) != 0 },
                };

            Default = null;
            if ((flags & ComTypes.PARAMFLAG.PARAMFLAG_FHASDEFAULT) != 0)
            {
                IntPtr ip = pdesc.lpVarValue + 8;
                var obj = Marshal.GetObjectForNativeVariant(ip);
                Default = obj.ToString();
            }
            GetRealType(info, desc.tdesc);
        }

        public Element(XElement node)
        {

            TypeName = node.Attribute("Type").Value;
            VariantType = 
                (VarEnum)Enum.Parse(
                    typeof(VarEnum), 
                    node.Attribute("VType").Value);

            Flags =
                new Dictionary<string, bool>
                {
                    { "In",     node.Attribute("In") != null },
                    { "Out",    node.Attribute("Out") != null },
                    { "Lcid",   node.Attribute("Lcid") != null },
                    { "Retval", node.Attribute("Retval") != null },
                    { "Option", node.Attribute("Option") != null },
                };
            Default = node.Attribute("Default")?.Value;
            if (Default != null)
            {

            }
        }

        public void SaveTo(XElement node)
        {
            node.Add(new XAttribute("Type", TypeName));
            node.Add(new XAttribute("VType", VariantType));
            foreach (var flag in Flags)
            {
                if (flag.Value)
                    node.Add(new XAttribute(flag.Key, true));
            }
            if (Default != null)
                node.Add(new XAttribute("Default", Default));
        }

        bool Check(ComTypes.PARAMFLAG f1, ComTypes.PARAMFLAG f2)
        {
            return (f1 & f2) == f2;
        }

        public VarEnum VariantType { get; private set; }

        public string TypeName { get; private set; }

        static Dictionary<VarEnum, string> _typenames =
            new Dictionary<VarEnum, string>
            {
                { VarEnum.VT_BOOL, "bool" },
                { VarEnum.VT_BSTR, "string" },
                { VarEnum.VT_I1, "sbyte" },
                { VarEnum.VT_UI1, "byte" },
                { VarEnum.VT_I2, "short" },
                { VarEnum.VT_UI2, "ushort" },
                { VarEnum.VT_I4, "int" },
                { VarEnum.VT_INT, "int" },
                { VarEnum.VT_UI4, "uint" },
                { VarEnum.VT_UINT, "uint" },
                { VarEnum.VT_I8, "long" },
                { VarEnum.VT_UI8, "ulong" },
                { VarEnum.VT_R4, "float" },
                { VarEnum.VT_R8, "double" },
                { VarEnum.VT_EMPTY, "null" },
                { VarEnum.VT_VOID, "void" },
                { VarEnum.VT_VARIANT, "Variant" },
                { VarEnum.VT_DISPATCH, "IDispatch" },
                { VarEnum.VT_PTR, "*" },
                { VarEnum.VT_SAFEARRAY, "[]" },
            };

        private void GetRealType(ComTypes.ITypeInfo info, ComTypes.TYPEDESC desc)
        {
            var vt = (VarEnum)desc.vt;
            var ip = desc.lpValue;
            switch (vt)
            {
                case VarEnum.VT_PTR:
                    GetRealType(info, Utils.GetObject<ComTypes.TYPEDESC>(ip));
                    break;
                case VarEnum.VT_SAFEARRAY:
                    VariantType |= VarEnum.VT_ARRAY;
                    TypeName = "[]" + TypeName;
                    GetRealType(info, Utils.GetObject<ComTypes.TYPEDESC>(ip));
                    break;
                case VarEnum.VT_USERDEFINED:
                    VariantType |= VarEnum.VT_DISPATCH;
                    int href;
                    href = ip.ToInt32();
                    ComTypes.ITypeInfo rinfo;
                    info.GetRefTypeInfo(href, out rinfo);
                    string name, doc, helpfile;
                    int hc;
                    rinfo.GetDocumentation(-1, out name, out doc, out hc, out helpfile);
                    TypeName = name + TypeName;
                    break;
                default:
                    VariantType |= vt;
                    if (_typenames.ContainsKey(vt))
                        TypeName = _typenames[vt] + TypeName;
                    else
                        TypeName = "object" + TypeName;
                    break;
            }

        }

        public override string ToString()
        {
            List<string> flags = new List<string>();
            if (Flags["Out"])
                flags.Add(Flags["In"] ? "ref" : "out");
            if (Flags["Option"])
                flags.Add("option");
            if (Default != null)
                flags.Add($"default({Default})");
            var sflags = 
                flags.Count > 0 ?
                $"[{string.Join(" ", flags)}]": "";
            return $"{sflags} {TypeName}";
        }

    }
}
